﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.ComponentModel;
using BGShell.Interop;
using System.Globalization;
using WindowsFormsAero;

namespace BGShell.PowerShell.Controls
{
    [Serializable]
    public sealed class ConsoleControlFormatEntry : ICloneable, IEquatable<ConsoleControlFormatEntry>, IFormattable
    {
        [Flags]
        private enum EntryFlags
        {
            None = 0x0,
            Bold = 0x1,
            Italic = 0x2,

            FontSize = 0x1000,
            ForegroundColor = 0x2000,
            BackgroundColor = 0x4000,
            
            BoldSet = 0x10000,
            ItalicSet = 0x20000,
        }

        private ConsoleControlFormatEntry _prototype;
        private EntryFlags _flags;

        private String _font;
        private Single _fontSize;
        private Color _background;
        private Color _foreground;

        public ConsoleControlFormatEntry()
        {
        }

        public ConsoleControlFormatEntry(Color foreColor)
        {
            ForeColor = foreColor;
        }

        public ConsoleControlFormatEntry(Color foreColor, Color backColor)
            : this(foreColor)
        {
            BackColor = backColor;
        }

        public ConsoleControlFormatEntry(Color foreColor, Color backColor, String fontName)
            : this (foreColor, backColor)
        {
            FontName = fontName;
        }

        public ConsoleControlFormatEntry(Color foreColor, Color backColor, String font, Single fontSize)
            : this (foreColor, backColor, font)
        {
            FontSize = fontSize;
        }

        [DefaultValue(null)]
        public Color? ForeColor
        {
            get 
            {
                var result = GetNullableValue(EntryFlags.ForegroundColor, _foreground);

                if ((result == null) && (_prototype != null))
                {
                    result = _prototype.ForeColor;
                }

                return result;
            }
            set { SetNullableValue(EntryFlags.ForegroundColor, ref _foreground, value); }
        }

        [DefaultValue(null)]
        public Color? BackColor
        {
            get
            {
                var result = GetNullableValue(EntryFlags.BackgroundColor, _background);

                if ((result == null) && (_prototype != null))
                {
                    result = _prototype.BackColor;
                }

                return result;
            }
            set { SetNullableValue(EntryFlags.BackgroundColor, ref _background, value); }
        }

        [DefaultValue(null)]
        public String FontName
        {
            get 
            {
                if ((_font == null) && (_prototype != null))
                {
                    return _prototype.FontName;
                }

                return _font; 
            }
            set { _font = value; }
        }

        [DefaultValue(null)]
        public Single? FontSize
        {
            get
            {
                var result = GetNullableValue(EntryFlags.FontSize, _fontSize);

                if ((result == null) && (_prototype != null))
                {
                    result = _prototype.FontSize;
                }

                return result;
            }
            set { SetNullableValue(EntryFlags.FontSize, ref _fontSize, value); }
        }

        [DefaultValue(false)]
        public Boolean Bold
        {
            get { return CheckPrototypeFlagAnd(EntryFlags.Bold, EntryFlags.BoldSet); }
            set 
            {
                SetFlag(EntryFlags.Bold, value);
                SetFlag(EntryFlags.BoldSet, true);
            }
        }

        [DefaultValue(false)]
        public Boolean Italic
        {
            get { return CheckPrototypeFlagAnd(EntryFlags.Italic, EntryFlags.ItalicSet); }
            set
            {
                SetFlag(EntryFlags.Italic, value);
                SetFlag(EntryFlags.ItalicSet, true);
            }
        
        }

        public ConsoleControlFormatEntry Clone()
        {
            return new ConsoleControlFormatEntry()
            {
                _prototype = this
            };
        }

        public override int GetHashCode()
        {
            return
                (FontName ?? String.Empty).GetHashCode() ^
                (BackColor ?? Color.Empty).GetHashCode() ^
                (ForeColor ?? Color.Empty).GetHashCode() ^
                (Bold).GetHashCode() ^
                (Italic).GetHashCode();
                
        }

        public override bool Equals(object obj)
        {
            return Equals(obj as ConsoleControlFormatEntry);
        }

        public bool Equals(ConsoleControlFormatEntry other)
        {
            if (ReferenceEquals(this, other))
            {
                return true;
            }

            return (other != null) &&
                (Bold == other.Bold) &&
                (Italic == other.Italic) &&
                (FontName == other.FontName) &&
                (BackColor == other.BackColor) &&
                (ForeColor == other.ForeColor);
        }

        public override string ToString()
        {
            return ToString(CultureInfo.InvariantCulture);
        }

        public string ToString(IFormatProvider formatProvider)
        {
            return ToString(null, formatProvider);
        }

        public string ToString(string format, IFormatProvider formatProvider)
        {
            return String.Format(formatProvider, "Fore={0}; Back={1}",
                ColorToString(_foreground), ColorToString(_background));
        }

        internal void Apply(RichTextFont font)
        {
            if (ForeColor.HasValue)
            {
                font.ForeColor = ForeColor.Value;
            }

            if (BackColor.HasValue)
            {
                font.BackColor = BackColor.Value;
            }

            font.Bold = Bold;
            font.Italic = Italic;

            if (!String.IsNullOrEmpty(FontName))
            {
                font.Name = FontName;
            }

            if (FontSize.HasValue)
            {
                font.Size = FontSize.Value;
            }
        }

        internal void Apply(ConsoleControl control)
        {
            if (ForeColor.HasValue)
            {
                control.ForeColor = ForeColor.Value;
            }

            if (BackColor.HasValue)
            {
                control.BackColor = BackColor.Value;
            }

            control.Font = GetFont();
        }

        internal Font GetFont()
        {
            if (String.IsNullOrEmpty(_font) || !CheckFlagAnd(EntryFlags.FontSize))
            {
                return null;
            }

            var style = FontStyle.Regular;

            if (Bold) style |= FontStyle.Bold;
            if (Italic) style |= FontStyle.Italic;

            return new Font(_font, _fontSize, style, GraphicsUnit.Point);
        }

        object ICloneable.Clone()
        {
            return Clone();
        }

        private bool CheckPrototypeFlagAnd(EntryFlags flag, EntryFlags flagSpecified)
        {
            if (CheckFlagAnd(flagSpecified))
            {
                return CheckFlagAnd(flag);
            }

            if (_prototype != null)
            {
                return _prototype.CheckPrototypeFlagAnd(flag, flagSpecified);
            }

            return false;
        }

        private bool CheckFlagAnd(EntryFlags flag)
        {
            return (_flags & flag) == flag;
        }

        private void SetFlag(EntryFlags flag, Boolean value)
        {
            _flags = value ? (_flags | flag) : (_flags & ~flag);
        }

        private void SetNullableValue<T>(EntryFlags flag, ref T storage, T? value) where T : struct
        {
            if (value.HasValue)
            {
                storage = value.Value;
                SetFlag(flag, true);
            }
            else
            {
                SetFlag(flag, false);
            }
        }

        private T? GetNullableValue<T>(EntryFlags flag, T storage) where T : struct
        {
            if (CheckFlagAnd(flag))
            {
                return storage;
            }

            return null;
        }

        private static string ColorToString(Color? c)
        {
            if (c.HasValue)
            {
                return ColorTranslator.ToHtml(c.Value);
            }

            return "(null)";
        }
    }

}
